//
//  AccreteDownloader.swift
//  ZLKit_SwiftDemo_Example
//
//  Created by iOS on 2022/8/5.
//  Copyright © 2022 CocoaPods. All rights reserved.
//

import Foundation
import Alamofire
import AVFoundation

public class AccreteDownloader: NSObject {
        
    enum LoadingState {
        /// 默认状态 -- 空、隐藏状态
        case normal
        /// 成功
        case success
        /// 加载中
        case loading
        /// 失败
        case failure
    }
    
    public let fileDownload: AccreteFileDownload = .init()
        
    /// 进度更新
    var progressSync: Params_one_void<Double> = nil
    var progress: Double = 0 {
        didSet {
            progressSync?(progress)
        }
    }
    
    /// 加载状态的同步
    var loadingStateSync: Params_one_void<LoadingState> = nil
    var loadingState: LoadingState = .loading {
        didSet {
            loadingStateSync?(loadingState)
        }
    }
    
    /// 设置图片信息
    public var setImage: Params_three_void<String, String, Params_one_void<CGSize>> = nil
        
    // 使用哪个质量的图
    public var quality: FileCacheDefaults.ImageFileType = .frame
    
    // 低精度的
    public var  lowAccuracy: Bool = true
    
    /// 优先尝试缩略图地址拼接
    /// 链接后面拼接参数，以控制图片的大小
    /// COS（腾讯云）拼参文档：https://cloud.tencent.com/document/product/436/44880         示例："?imageMogr2/thumbnail/2097152@"
    /// OSS（阿里云）拼参文档：https://help.aliyun.com/document_detail/44688.html              示例："?x-oss-process=image/resize,w_\(width_int),m_lfit"
    public var priorityTryThumbnailUrlAppend: Basic_return<String> = nil
    
    /// 获取图片的宽高
    ///
    /// 📢 如果本类没有被ZLDownloaderImageView对象或其派生类持有，则需要开发者手动调用一下 fire 函数进行启动下载动作
    public var getImageSize: Params_one_void<CGSize> = nil
    
    private let progressNotificationName: Notification.Name = .init(rawValue: "ProgressNotificationName")
    private let successNotificationName: Notification.Name = .init(rawValue: "SuccessNotificationName")
    private let failNotificationName: Notification.Name = .init(rawValue: "FailNotificationName")
    public var isOnRequest: Bool = false
    
    private var remote = false
    
    deinit {
        if isOnRequest {
            if ZLDownloaderUrlWhiteList.shared.whiteList.contains(self.url) {
                let index = ZLDownloaderUrlWhiteList.shared.whiteList.firstIndex(of: self.url) ?? 0
                ZLDownloaderUrlWhiteList.shared.whiteList.remove(at: index)
            }
        }
    }
    
    var outputStream: OutputStream? = nil
                    
    public init(url: String? = nil) {
        super.init()
        guard let url = url else { return }
        // 注册下载时的响应事件
        registerAction()
        self.url = url
        addNotification(name: self.progressNotificationName, observer: self, selector: #selector(progressNotificationAction(_:)))
        addNotification(name: self.successNotificationName, observer: self, selector: #selector(successNotificationAction(_:)))
        addNotification(name: self.failNotificationName, observer: self, selector: #selector(failNotificationAction(_:)))
    }
        
    func registerAction() {
        fileDownload.progress {[weak self] bytesRead, totalBytesRead, progrss in
            guard let self = self else { return }
            postNotification(self.progressNotificationName, userInfo: ["progrss": progrss, "url": self.url])
        }.success {[weak self] reponse in
            guard let self = self else { return }
            postNotification(self.successNotificationName, userInfo: ["reponse": reponse, "url": self.url])
            if ZLDownloaderUrlWhiteList.shared.whiteList.contains(self.url) {
                let index = ZLDownloaderUrlWhiteList.shared.whiteList.firstIndex(of: self.url) ?? 0
                ZLDownloaderUrlWhiteList.shared.whiteList.remove(at: index)
            }
        }.fail {[weak self] error in
            guard let self = self else { return }
            if !self.didTryChineseEncoding {
                self.didTryChineseEncoding = true
                self.fileDownload.accreteIndex = 0
                let encoding_url = self.url.URLChineseEncoding()
                self.verifyOSS(remoteUrl: encoding_url, remote: self.remote)
                return
            }
            if !self.didTryRemoveParams {
                self.didTryRemoveParams = true
                self.fileDownload.accreteIndex = 0
                self.verifyOSS(remoteUrl: self.url, remote: self.remote)
                return
            }
            if ZLDownloaderUrlWhiteList.shared.whiteList.contains(self.url) {
                let index = ZLDownloaderUrlWhiteList.shared.whiteList.firstIndex(of: self.url) ?? 0
                ZLDownloaderUrlWhiteList.shared.whiteList.remove(at: index)
            }
            postNotification(self.failNotificationName, userInfo: ["error": error?.localizedDescription ?? "", "url": self.url])
        }
    }
    
    // TODO: - Private
    
    // 当一个请求失败后，自动尝试中文编码，再次发送一次请求
    private var didTryChineseEncoding: Bool = false
    // 当一个请求失败后，自动尝试去除链接参数，再次发送一次请求
    var didTryRemoveParams: Bool = false
    
    // 图片地址
    public var url: String = ""
            
}

// Public
extension AccreteDownloader {
    
    /// 在不设置图片到视图的情况下，使用该函数进行预加载图片，以获取图片尺寸的大小
    public func fire() {
        let isExistOriginData = Self.isExistOriginData(url: url, didTryRemoveParams: didTryRemoveParams, lowAccuracy: lowAccuracy, thumbnailUrlParams: (priorityTryThumbnailUrlAppend?() ?? ""))
        verifyOSS(remoteUrl: url, remote: !isExistOriginData)
    }
        
    /// 发起加载动作的唯一函数
    public func startRequest(remote: Bool) {
        self.remote = remote
        progress = 0
        didTryChineseEncoding = false
        verifyOSS(remoteUrl: url, remote: remote)
    }
    
    // 缓存中是否已经存在原始数据
    public static func isExistOriginData(url: String, didTryRemoveParams: Bool, lowAccuracy: Bool, thumbnailUrlParams: String) -> Bool {
        let safeUrl = fixUrl(remoteUrl: url, didTryRemoveParams: didTryRemoveParams, lowAccuracy: lowAccuracy, thumbnailUrlParams: thumbnailUrlParams)
        return FileCacheDefaults.isExist(remoteUrl: safeUrl, fileType: discernFileType(remoteUrl: url), quality: .origin)
    }
    
}

// Notifications
extension AccreteDownloader {
    
    @objc func progressNotificationAction(_ notifi: NSNotification) {
        let progrss = notifi.userInfo?["progrss"] as? Double ?? 0
        let url = notifi.userInfo?["url"] as? String ?? ""
        guard url == self.url, loadingState != .success else { return }
        self.progress = progrss
        if self.loadingState != .loading {
            self.loadingState = .loading
        }
    }
    
    @objc func successNotificationAction(_ notifi: NSNotification) {
        let reponse = notifi.userInfo?["reponse"] as? Any
        let url = notifi.userInfo?["url"] as? String ?? ""
        guard url == self.url, loadingState != .success else { return }
        if self.loadingState != .success {
            self.loadingState = .success
        }
        DispatchQueue.global().async {[weak self] in
            self?.onDownloadSuccess(reponse: reponse)
        }
    }
    
    @objc func failNotificationAction(_ notifi: NSNotification) {
        let url = notifi.userInfo?["url"] as? String ?? ""
        guard url == self.url, loadingState != .success else { return }
        if self.loadingState != .failure {
            self.loadingState = .failure
        }
    }
    
}

// Private
extension AccreteDownloader {
    
    private static func fixUrl(remoteUrl: String, didTryRemoveParams: Bool, lowAccuracy: Bool, thumbnailUrlParams: String) -> String {
        var safeUrl = remoteUrl
        if !didTryRemoveParams && lowAccuracy { // OSS存储源
            safeUrl = safeUrl + thumbnailUrlParams
        }else {
            // 其他图片未知的图片存储源，不进行处理
        }
//        print("safeUrl = \(safeUrl)")
        return safeUrl
    }
    
    // 识别OSS图片链接，自动拼接控件的宽度作为获取条件
    private func verifyOSS(remoteUrl: String, remote: Bool) {
        DispatchQueue.main.async {[weak self] in
            guard let self = self else { return }
            let safeUrl = Self.fixUrl(remoteUrl: remoteUrl, didTryRemoveParams: self.didTryRemoveParams, lowAccuracy: self.lowAccuracy, thumbnailUrlParams: (self.priorityTryThumbnailUrlAppend?() ?? ""))
            if remote {
                self.loadRemoteData(from: safeUrl)
            }else {
                self.quality == .origin ? self.loadOriginImage(from: safeUrl) : self.loadProcessedImage(from: safeUrl)
            }
        }
    }
    
    // 从远程加载
    private func loadRemoteData(from remoteUrl: String) {
        let path = FileCacheDefaults.getSandboxUrl(remoteUrl: remoteUrl, fileType: Self.discernFileType(remoteUrl: remoteUrl), quality: .origin).path
        fileDownload.download(from: remoteUrl, to: path)
    }
    
    /// 加载原图
    private func loadOriginImage(from remoteUrl: String) {
        DispatchQueue.global().async {[weak self] in
            guard let self = self else { return }
            // 从缓存中找原始图片数据
            let isExist = FileCacheDefaults.isExist(remoteUrl: remoteUrl, fileType: .image, quality: .origin)
            guard !isExist else {
                // 缓存中存在原始数据
                let path = FileCacheDefaults.getSandboxUrl(remoteUrl: remoteUrl, fileType: .image, quality: .origin).path
                DispatchQueue.main.async {[weak self] in
                    guard let self = self else { return }
                    self.setImage(path: path)
                }
                return
            }
        }
    }
    
    /// 交互
    private func setImage(path: String) {
        // 展示
        if let setImage = self.setImage {
            setImage(path, url) {[weak self] imageSize in
                guard let self = self else { return }
                self.getImageSize?(imageSize)
                self.loadingState = .normal
            }
        }
        if let getImageSize = self.getImageSize {
            DispatchQueue.global().async {
                let image = UIImage(contentsOfFile: path)
                DispatchQueue.main.async {[weak self] in
                    getImageSize(image?.size ?? .zero)
                    self?.loadingState = .normal
                }
            }
        }
    }
    
    /// 加载经过特殊处理后的图片
    private func loadProcessedImage(from remoteUrl: String) {
        let fileType: FileCacheDefaults.FileType = .image
        // 从缓存中找已经加工过的图片数据
        var isExist = FileCacheDefaults.isExist(remoteUrl: remoteUrl, fileType: fileType, quality: .frame)
        guard !isExist else {
            // 缓存中存在
            let path = FileCacheDefaults.getSandboxUrl(remoteUrl: remoteUrl, fileType: fileType, quality: .frame).path
            setImage(path: path)
            return
        }
        // 从缓存中找原始图片数据
        isExist = FileCacheDefaults.isExist(remoteUrl: remoteUrl, fileType: fileType, quality: .origin)
        guard !isExist else {
            // 缓存中存在原始数据
            let path = FileCacheDefaults.getSandboxUrl(remoteUrl: remoteUrl, fileType: fileType, quality: .origin).path
            let data = (try? Data(contentsOf: .init(fileURLWithPath: path))) ?? .init()
            if isGif(data: data) {
                // 如果是gif，就只加载首帧图
                disassemblyGif(data: data, remoteUrl: remoteUrl)
                return
            }
            // 加工数据
            tailorAndCompress(origin: data, from: remoteUrl)
            return
        }
        if loadingState != .failure {
            loadingState = .failure
        }
        print("未从缓存中找到相关文件，使用该函数之前应先判断是否存在于缓存中")
    }
    
    /// 成功的事件
    private func onDownloadSuccess(reponse: Any?) {
        DispatchQueue.global().async {[weak self] in
            guard let self = self else { return }
            guard let reponse = reponse as? DownloadResponse<Data> else { return }
            let data = reponse.value ?? .init()
            let remoteUrl = reponse.response?.url?.absoluteString ?? ""
            if self.quality == .origin {
                // 使用原图
                let path = FileCacheDefaults.getSandboxUrl(remoteUrl: remoteUrl, fileType: .image, quality: .origin).path
                DispatchQueue.main.async {[weak self] in
                    guard let self = self else { return }
                    self.setImage(path: path)
                }
                return
            }
            if self.isGif(data: data) {
                // 如果是gif，就只加载首帧图
                self.disassemblyGif(data: data, remoteUrl: remoteUrl)
                return
            }
            // 加工数据
            self.tailorAndCompress(origin: data, from: remoteUrl)
        }
    }
    
    // 解体gif图
    private func disassemblyGif(data: Data, remoteUrl: String) {
        // 如果是gif，就只加载首帧图
        self.getFirstImageWithGIF(data: data, completion: {[weak self] firstImage in
            guard let self = self else { return }
            let gifFirstImageData = firstImage?.jpegData(compressionQuality: 1.0) ?? .init()
            
            // 加工数据
            self.tailorAndCompress(origin: gifFirstImageData, from: remoteUrl)
        })
    }
    
    /// 获取Gif首帧图
    private func getFirstImageWithGIF(data: Data, completion: Params_one_void<UIImage?>) {
        DispatchQueue.global().async {
            if let source = CGImageSourceCreateWithData(data as CFData, nil), let imageRef = CGImageSourceCreateImageAtIndex(source, 0, nil) {
                let image = UIImage(cgImage: imageRef, scale: CGFloat(0), orientation: UIImage.Orientation.up)
                DispatchQueue.main.async {
                    completion?(image)
                }
            }
        }
    }
    
    private func isGif(data: Data) -> Bool {
        if let source = CGImageSourceCreateWithData(data as CFData, nil) {
            return CGImageSourceGetCount(source) > 1
        }
        return false
    }
    
    // 对原始数据进行加工处理
    private func tailorAndCompress(origin data: Data, from remoteUrl: String) {
        // 加工数据
        let fileType = Self.discernFileType(remoteUrl: remoteUrl)
        if fileType == .video {
            // 视频存储
            DispatchQueue.global().async {[weak self] in
                guard let self = self else { return }
                // 分离首帧图
                let avAsset = AVAsset(url: .init(fileURLWithPath: FileCacheDefaults.getSandboxUrl(remoteUrl: remoteUrl, fileType: fileType, quality: .origin).path))
                let generator = AVAssetImageGenerator(asset: avAsset)
                generator.appliesPreferredTrackTransform = true
                let time = CMTimeMakeWithSeconds(0, preferredTimescale: 600)
                var actualTime:CMTime = CMTimeMake(value: 0,timescale: 0)
                let imageRef = try? generator.copyCGImage(at: time, actualTime: &actualTime)
                guard let imageRef = imageRef, let data = UIImage(cgImage: imageRef).jpegData(compressionQuality: 0.7) else { return }
                // 写入原图数据
                let path = FileCacheDefaults.getSandboxUrl(remoteUrl: remoteUrl, fileType: .image, quality: .origin).path
                self.outputStream = nil
                self.outputStream = .init(toFileAtPath: path, append: false)
                self.outputStream?.open()
                self.outputStream?.write(data ?? .init())
                self.outputStream?.close()
                self.outputStream = nil
                // 写入缩略图数据
                self.tailorAndCompress(data: data, lowAccuracy: self.lowAccuracy, result: {[weak self] resp in
                    guard let self = self else { return }
                    var data: Data? = nil
                    if let image = resp as? UIImage {
                        data = image.jpegData(compressionQuality: 0.7)
                    }
                    if let tempData = resp as? Data {
                        data = tempData
                    }
                    let path = FileCacheDefaults.getSandboxUrl(remoteUrl: remoteUrl, fileType: .image, quality: .frame).path
                    self.outputStream = nil
                    self.outputStream = .init(toFileAtPath: path, append: false)
                    self.outputStream?.open()
                    self.outputStream?.write(data ?? .init())
                    self.outputStream?.close()
                    self.outputStream = nil
                    // 设置图片
                    DispatchQueue.main.async {[weak self] in
                        guard let self = self else { return }
                        self.setImage(path: path)
                    }
                })
            }
            return
        }
        // 图片压缩
        tailorAndCompress(data: data, lowAccuracy: lowAccuracy, result: { resp in
            // 图片存储
            DispatchQueue.global().async {[weak self] in
                guard let self = self else { return }
                var data: Data? = nil
                if let image = resp as? UIImage {
                    data = image.jpegData(compressionQuality: 0.7)
                }
                if let tempData = resp as? Data {
                    data = tempData
                }
                
                // 写入数据
                let path = FileCacheDefaults.getSandboxUrl(remoteUrl: remoteUrl, fileType: fileType, quality: .frame).path
                self.outputStream = nil
                self.outputStream = .init(toFileAtPath: path, append: false)
                self.outputStream?.open()
                self.outputStream?.write(data ?? .init())
                self.outputStream?.close()
                self.outputStream = nil
                
                DispatchQueue.main.async {[weak self] in
                    guard let self = self else { return }
                    self.setImage(path: path)
                }
            }
        })
    }
    
    /// 识别数据格式
    public static func discernFileType(remoteUrl: String) -> FileCacheDefaults.FileType {
        let videoFormats = ["mp4", "MP4", "mov", "MOV"]
        guard remoteUrl.contains(".") else { return .image }
        let key = remoteUrl.ns.components(separatedBy: ".").last ?? ""
        guard videoFormats.contains(key) else { return .image }
        return .video
    }
    
}

extension OutputStream {
    
    @discardableResult
    func write(_ data: Data) -> Int {
        return data.withUnsafeBytes { pointer in
            return self.write(pointer, maxLength: data.count)
        }
    }
}

